package timeline.util;



import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.*;

/**
 *
 * the class for control thread switch
 * by lianghailun
 * Created by lianghailun on 2017/6/20 0020.
 * by zhangweijie
 * modify in 2017/09
 */
public class TimeLine {
    private class RunningState{
    	public long startTime;
        public List<RunningTask> runningTasks;
        public CountDownLatch beforeLatch; // make sure all threads run with main at the same time
        public CountDownLatch afterLatch;  // this latch is to divide level
        public ConcurrentHashMap<String,Object> map;  // the object to deliver msg in all functions
    }
    private static TimeLine kernel = null;

    private static ExecutorService fixedThreadsPool;
    private static ExecutorService mainpool;
    private static int mainPoolSize =1;
    private static int exePoolSize=4;

    /**
     * the Builder for TimeLine
     * @author Administrator
     *
     */
    public static class Builder{
        private List<Object> objects;
        public Builder() {
			// TODO Auto-generated constructor stub
    		objects = new ArrayList<Object>();
		}
        
        public Builder setPoolSize(int _mainPoolSize,int _exePoolSize){
        	mainPoolSize=_mainPoolSize;
        	exePoolSize=_exePoolSize;
        	System.out.println("_mainPoolSize:"+_mainPoolSize+"  _exePoolSize:"+_exePoolSize);
        	return this;
        }
    	
    	public Builder addLib(Object object) {
			objects.add(object);
			return this;
		}
    	
    	public boolean commit(){
    		if(kernel==null){
                kernel = new TimeLine();
                return true;
    		}
    		return false;

    	}
    	
    	public void run(){
            if(kernel==null)
                kernel = new TimeLine();

            kernel.startSteinsGate(objects,null);
    	}
    	
    	public void run(RunFinishResult runFinishResult){
            if(kernel==null)
                kernel = new TimeLine();

            kernel.startSteinsGate(objects,runFinishResult);
    	}
    }

    public static void shutdown(){
        fixedThreadsPool.shutdown();
        mainpool.shutdown();
    }

    private TimeLine(){
        fixedThreadsPool = Executors.newScheduledThreadPool(exePoolSize);
        mainpool = Executors.newScheduledThreadPool(mainPoolSize);
    }

    /**
     * run the objects with methods
     */
    private  void  startSteinsGate(List<Object> objs,RunFinishResult runFinishResult){
        List<RunningTask> runningTasks = new ArrayList<>();
        for (Object obj : objs) {
            runningTasks.addAll(trackTask(obj, obj.getClass()));//parse each obj thread
        }
        startRunInPool(runningTasks,runFinishResult);

    }


    private synchronized void startRunInPool(List<RunningTask> runningTasks,RunFinishResult runFinishResult) {
        RunningState state = new RunningState();
        state.runningTasks = runningTasks;
        state.map = new ConcurrentHashMap<String,Object>();
        state.startTime = System.currentTimeMillis();
        mainpool.submit(new MainRunnable(state,runFinishResult));
    }

    private class MainRunnable implements Runnable{
        private RunningState state;
        private RunFinishResult runFinishResult;

        public MainRunnable( RunningState state,RunFinishResult runFinishResult) {
            this.state = state;
            this.runFinishResult=runFinishResult;
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            try {
                for (final RunningTask task : state.runningTasks) {
                    state.beforeLatch = new CountDownLatch(1);//set the latch make sure all run in same time
                    int sum = 0;
                    for(Integer i:task.copys){
                        sum += i;
                    }
                    state.afterLatch = new CountDownLatch(sum);// count down all thread in a level
                    for(int i=0;i<task.methods.size();i++){
                    	int total = task.copys.get(i);
                        for(int j=0;j<total;j++){
                            fixedThreadsPool.submit(new MyRunnable(total,task.obj, task.methods.get(i), j,state));
                        }
                    }
                    state.beforeLatch.countDown();
                    try {
                        state.afterLatch.await();// wait for a level to finish
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis() - start + "ms");
            if(runFinishResult!=null){
            	state.map.put("mainScheduleDuration", System.currentTimeMillis() - start + "ms");
            	runFinishResult.onResult(state.map);
            }
        }
    }

    private class MyRunnable implements Runnable{
        private Object object;
        private Method method;
        private int copy;
        private RunningState state ;
        private int total;

        public MyRunnable(int total,Object object, Method method, int copy,RunningState state ) {
            this.object = object;
            this.method = method;
            this.copy = copy;
            this.state = state;
            this.total = total;
        }
        @Override
        public void run() {
            try {
                state.beforeLatch.await();// wait for mainThread
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                RunningMsg msg = new RunningMsg();
                msg.copy = copy;
                msg.maps = state.map;
                msg.total = total;
                msg.taskStartTime = state.startTime;
                msg.msgInitTime = System.currentTimeMillis();
                method.invoke(object,msg);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            state.afterLatch.countDown();// countdown for a level
        }
    }

    /**
     * bind method in a object
     * @param obj
     * @param cl
     */
    public  List<RunningTask> trackTask( Object obj,Class<?> cl) {
        List<RunningTask> runningTasks = new ArrayList<>();
        for (Method m : cl.getDeclaredMethods()) {
            TLine tick = m.getAnnotation(TLine.class);
            if (tick != null) {
                System.out.println("parsing Level:" + tick.level() + "copy"
                        + tick.copy());
                boolean isAready = false;
                for(RunningTask runningTask : runningTasks){
                    if(runningTask.level == tick.level()){
                        isAready = true;
                        runningTask.methods.add(m);
                        runningTask.copys.add(tick.copy());
                        runningTask.obj = obj;
                    }
                }
                if(isAready){
                    continue;
                }else{
                    RunningTask runningTask = new RunningTask();
                    runningTask.level = tick.level();
                    runningTask.copys = new ArrayList<Integer>();
                    runningTask.methods = new ArrayList<Method>();
                    runningTask.methods.add(m);
                    runningTask.copys.add(tick.copy());
                    runningTask.obj = obj;
                    runningTasks.add(runningTask);
                }
            }

        }
        Collections.sort(runningTasks,new LevelComparator());
        return runningTasks;
    }

    /**
     * sort for level
     */
    private class  LevelComparator implements Comparator{


        @Override
        public int compare(Object o1, Object o2) {
            RunningTask t1= (RunningTask) o1;
            RunningTask t2= (RunningTask) o2;
            return t1.level-t2.level;
        }
    }

    /**
     * model for running
     */
    private class RunningTask{
        public int level = 0;
        public Object obj;
        public List<Method> methods;
        public List<Integer> copys;
    }
    
    /**
     * message for running
     * @author Administrator
     *
     */
    public static class RunningMsg implements Serializable{
    	public int copy;
    	public int total;
    	public long taskStartTime;
    	public long msgInitTime;
    	public ConcurrentHashMap<String, Object> maps;
    }

    /**
     * Created by Administrator on 2017/6/17 0017.
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TLine {
        int level() default 1;
        int copy() default 1;
    }
    
    public interface RunFinishResult{
    	public void onResult(ConcurrentHashMap<String, Object> map);
    }
}

